/** @file         lzm_mlog_api.c
 *  @brief        简要说明
 *  @details      详细说明
 *  @author       lzm
 *  @date         2022-10-25 12:25:56
 *  @version      v1.0
 *  @copyright    Copyright By lizhuming, All Rights Reserved
 *
 **********************************************************
 *  @LOG 修改日志:
 **********************************************************
*/

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>

#include "lzm_mlog_api.h"
#include "lzm_mlog_cfg.h"

#if 0 /* 这段代码先放着，是南向接口适配方案的一种 */
#include "lzm_api_common.h"
struct lzm_mlog_ctr_s
{
    lzm_ecode_t (*output)(const char *log, size_t size);
    lzm_ecode_t (*lock_func)(void *lock, uint32_t timer_out);
    lzm_ecode_t (*unlock_func)(void *lock);
    lzm_ecode_t (*get_time)(void);
};
typedef struct lzm_mlog_ctr_s lzm_mlog_ctr_t;
#endif

/* every line log's buffer */
static char log_buf[LZM_MLOG_LINE_BUF_SIZE] = { 0 };

/* level output info */
static const char *level_output_info[] = {
        [LZM_MLOG_LVL_ASSERT]  = "A",
        [LZM_MLOG_LVL_ERROR]   = "E",
        [LZM_MLOG_LVL_WARN]    = "W",
        [LZM_MLOG_LVL_INFO]    = "I",
        [LZM_MLOG_LVL_DEBUG]   = "D",
        [LZM_MLOG_LVL_VERBOSE] = "V",
};


extern void lzm_mlog_port_output(const char *log, size_t size);
extern void lzm_mlog_port_output_lock(void);
extern void lzm_mlog_port_output_unlock(void);
extern char* lzm_mlog_port_get_time(void);


#define zm_mlog_port_output lzm_mlog_port_output
#define zm_mlog_output_lock lzm_mlog_port_output_lock
#define zm_mlog_output_unlock lzm_mlog_port_output_unlock

void (*lzm_mlog_assert_hook)(const char* expr, const char* func, size_t line);

/**
 * another copy string function
 *
 * @param cur_len current copied log length, max size is LZM_MLOG_LINE_BUF_SIZE
 * @param dst destination
 * @param src source
 *
 * @return copied length
 */
size_t zm_mlog_strcpy(size_t cur_len, char *dst, const char *src)
{
    const char *src_old = src;

    LZM_MLOG_ASSERT("dst is null", dst);
    LZM_MLOG_ASSERT("src is null", src);

    while (*src != 0) {
        /* make sure destination has enough space */
        if (cur_len++ < LZM_MLOG_LINE_BUF_SIZE) {
            *dst++ = *src++;
        } else {
            break;
        }
    }
    return src - src_old;
}

/** 
 * @brief  elog_output
 * 
 * @param level level
 * @param tag tag
 * @param file file name
 * @param func function name
 * @param line line number
 * @param format output format
 * @param ... args
 * 
 * @retval 0: success
 * @author lizhuming
 */
void zm_mlog_output(uint8_t level, uint8_t fmt, const char *tag, const char *file, const char *func, const long line, const char *format, ...)
{
    size_t log_len = 0;
    va_list args;
    int fmt_result = 0;

    LZM_MLOG_ASSERT("level error", level <= LZM_MLOG_LVL_VERBOSE);

    /* args point to the first variable parameter */
    va_start(args, format);

    zm_mlog_output_lock();

    memset(log_buf, 0x00, LZM_MLOG_LINE_BUF_SIZE);

    /* FMT */
    /* FMT LEVEL */
    if(fmt&LZM_MLOG_FMT_LVL)
    {
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "[");
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, level_output_info[level]);
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "]");
    }
    /* FMT TAG */
    if(fmt&LZM_MLOG_FMT_TAG)
    {
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "[");
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, tag);
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "]");
    }
    /* FMT TIME */
    if(fmt&LZM_MLOG_FMT_TIME)
    {
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "[");
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "]");
    }
    /* FMT FILE */
    if(fmt&LZM_MLOG_FMT_FILE)
    {
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "[");
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, file);
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "]");
    }
    /* FMT TASK */
    if(fmt&LZM_MLOG_FMT_TASK)
    {
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "[");
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "]");
    }
    /* FMT FUNC */
    if(fmt&LZM_MLOG_FMT_FUNC)
    {
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "[");
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, func);
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "]");
    }
    /* FMT LINE */
    if(fmt&LZM_MLOG_FMT_LINE)
    {
        char line_num[LZM_MLOG_FMT_LINE_MAX_LEN + 1] = { 0 };
        snprintf(line_num, LZM_MLOG_FMT_LINE_MAX_LEN, "%ld", line);
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "[");
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, line_num);
        log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "]");
    }


    /* ":" */
    log_len += zm_mlog_strcpy(log_len, log_buf + log_len, ":");

    /* package other log data to buffer. '\0' must be added in the end by vsnprintf. */
    fmt_result = vsnprintf(log_buf + log_len, LZM_MLOG_LINE_BUF_SIZE - log_len, format, args);

    va_end(args);
    /* calculate log length */
    if ((log_len + fmt_result <= LZM_MLOG_LINE_BUF_SIZE) && (fmt_result > -1)) {
        log_len += fmt_result;
    } else {
        /* using max length */
        log_len = LZM_MLOG_LINE_BUF_SIZE;
    }

    /* \r\n */
    log_len += zm_mlog_strcpy(log_len, log_buf + log_len, "\r\n");

    zm_mlog_port_output(log_buf, log_len);
    zm_mlog_output_unlock();
}

/** 
  * @brief 
  * @details 
  * @param 
  * @retval 
  * @author lizhuming
  */
void lzm_mlog_assert_set_hook(void (*hook)(const char* expr, const char* func, size_t line)) {
    lzm_mlog_assert_hook = hook;
}

